/**
 * Created by PengLing on 2017/3/27.
 */
"use strict";

const pathToRegexp = require("path-to-regexp");
const util = require("util");
const moment = require("moment");

class ExcludesUrls {
  constructor(opts) {
    this.allUrls = undefined;
    this.reload(opts);
  }

  sanitizePath(path) {
    if (!path) return "/";
    const p =
      "/" +
      path
        .replace(/^\/+/i, "")
        .replace(/\/+$/, "")
        .replace(/\/{2,}/, "/");
    return p;
  }

  reload(opts) {
    // load all url
    if (!this.allUrls) {
      this.allUrls = opts;
      let that = this;
      this.allUrls.forEach(function (url, i, arr) {
        if (typeof url === "string") {
          url = { p: url, o: "*" };
          arr[i] = url;
        }
        const keys = [];
        let eachPath = url.p;
        url.p =
          !eachPath || eachPath === "(.*)" || util.isRegExp(eachPath)
            ? eachPath
            : that.sanitizePath(eachPath);
        url.pregexp = pathToRegexp(eachPath, keys);
      });
    }
  }

  isExcluded(path, method) {
    return this.allUrls.some(function (url) {
      return (
        !url.auth &&
        url.pregexp.test(path) &&
        (url.o === "*" || url.o.indexOf(method) !== -1)
      );
    });
  }
}

/**
 * 判断Url是否不鉴权
 * @param {*} opts {exclude: [*] or []}，'*'或['*']:跳过所有路由; []:所有路由都要验证
 * @param {*} path 当前request的path
 * @param {*} method 当前request的method
 */
let isPathExcluded = function (opts, path, method) {
  let excludeAll = Boolean(
    opts.exclude && opts.exclude.length && opts.exclude[0] == "*"
  );
  let excludes = null;
  if (!excludeAll) {
    let excludeOpts = opts.exclude || [];
    excludeOpts.push({ p: "/login", o: "POST" });
    excludeOpts.push({ p: "/logout", o: "PUT" });
    excludeOpts.push({ p: "/intellectualProperty/getTotalDisclosureCount", o: "GET" });
    excludeOpts.push({ p: "/intellectualProperty/getDepartmentRanking", o: "GET" });
    excludeOpts.push({ p: "/intellectualProperty/getDetailedStats", o: "GET" });
    excludeOpts.push({ p: "/intellectualProperty/getAcceptedCasesRanking", o: "GET" });
    excludeOpts.push({ p: "/intellectualProperty/getTypeDistribution", o: "GET" });
    excludeOpts.push({ p: "/intellectualProperty/getStats", o: "GET" });
    excludeOpts.push({ p: "/intellectualProperty/getProductRelatedIPStats", o: "GET" });
    excludeOpts.push({ p: "/patentManagement/list", o: "GET" });
    excludeOpts.push({ p: "/patentManagement/edit/:id", o: "POST" });
    excludeOpts.push({ p: "/patentManagement/delete/:id", o: "DELETE" });
    excludeOpts.push({ p: "/patentManagement/import", o: "POST" });
    excludeOpts.push({ p: "/patentManagement/export", o: "GET" });
    excludeOpts.push({ p: "/patentManagement/changePatentee/:id", o: "POST" });
    excludeOpts.push({ p: "/softwareCopyright/list", o: "GET" });
    excludeOpts.push({ p: "/softwareCopyright/import", o: "POST" });
    excludeOpts.push({ p: "/softwareCopyright", o: "POST" });
    excludeOpts.push({ p: "/softwareCopyright/:id", o: "PUT" });
    excludeOpts.push({ p: "/softwareCopyright/:id", o: "DELETE" });
    excludeOpts.push({ p: "/softwareCopyright/export", o: "GET" });
    excludeOpts.push({ p: "/softwareCopyright/template", o: "GET" }); 
    excludeOpts.push({ p: "/brandManagement/list", o: "GET" });
    excludeOpts.push({ p: "/brandManagement/:id", o: "GET" });
    excludeOpts.push({ p: "/brandManagement", o: "POST" });
    excludeOpts.push({ p: "/brandManagement/:id", o: "PUT" });
    excludeOpts.push({ p: "/brandManagement/:id", o: "DELETE" });
    excludeOpts.push({ p: "/brandManagement/batch-delete", o: "POST" });
    excludeOpts.push({ p: "/brandManagement/export", o: "GET" });
    excludeOpts.push({ p: "/brandManagement/import", o: "POST" });
    
    excludes = new ExcludesUrls(excludeOpts);
  }
  let excluded = excludeAll || excludes.isExcluded(path, method);
  return excluded;
};

let authorizeToken = async function (ctx, token) {
  let rslt = null;
  const tokenFormatRegexp =
    /^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/g;
  if (token && tokenFormatRegexp.test(token)) {
    try {
      const authorizeRes = await ctx.fs.dc.models.UserToken.findOne({
        where: {
          token: token,
        },
      });
      const { userInfo, expired } = authorizeRes;

      if (expired && moment().valueOf() <= moment(expired).valueOf()) {
        rslt = {
          authorized: userInfo.authorized,
          resources: (userInfo || {}).resources || [],
        };
        ctx.fs.api.userId = userInfo.id;
        ctx.fs.api.userName = userInfo.username;
        ctx.fs.api.userInfo = userInfo;
        ctx.fs.api.token = token;
        ctx.fs.api.departmentId=userInfo.departmentId;
        ctx.fs.api.departmentName=userInfo.departmentName
        // ctx.fs.api.userId = authorizeRes.userInfo.id;
        ctx.fs.api.userPortal = Array.isArray(userInfo.portals)
          ? userInfo.portals.find((f) => f.portal == "A")
            ? "A"
            : "CC"
          : "CC";
        ctx.fs.api.orgtype = userInfo.orgtype;
        // ctx.fs.api.userInfo = authorizeRes.userInfo;
        ctx.fs.api.organizationId = userInfo.orgId;
      }
    } catch (err) {
      const { error } = err.response || {};
      ctx.fs.logger.log(
        "[anxinyun]",
        "[AUTH] failed",
        (error || {}).message || `cannot GET /users/${token}`
      );
    }
  }
  return rslt;
};

let isResourceAvailable = function (resources, options) {
  let authCode = null;
  // authorize user by authorization attribute
  const { authAttr, method, path } = options;
  for (let prop in authAttr) {
    let keys = [];
    let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, "(\\d+)"), keys);
    if (re.test(`${method}${path}`)) {
      authCode = authAttr[prop];
      break;
    }
  }
  return !authCode || (resources || []).some((code) => code === authCode);
};

function factory(app, opts) {
  return async function auth(ctx, next) {
    const { path, method, header, query } = ctx;
    ctx.fs.logger.log("[AUTH] start", path, method);
    ctx.fs.api = ctx.fs.api || {};
    ctx.fs.port = opts.port;
    ctx.redis = app.redis;
    ctx.redisTools = app.redisTools;
    let error = null;
   

    if (path) {
      if (!isPathExcluded(opts, path, method)) {
        const user = await authorizeToken(ctx, header.token || query.token);
        if (user && user.authorized) {

          // if (!isResourceAvailable(user.resources, { authAttr: app.fs.auth.authAttr, path, method })) {
          //     error = { status: 403, name: 'Forbidden' }
          // } else {
          //     error = { status: 401, name: 'Unauthorized' }
          // }
        } else {
          error = { status: 401, name: "Unauthorized" };
        }
      }
      // else {
      //    const authorizeRes = await ctx.fs.dc.models.UserToken.findOne({
      //       where: {
      //          token: header.token
      //       }
      //    })

      //    if (authorizeRes && authorizeRes.userInfo) {
      //       ctx.fs.api.userId = authorizeRes.userInfo.id;
      //       ctx.fs.api.userPortal = Array.isArray(authorizeRes.userInfo.portals) ? authorizeRes.userInfo.portals.find(f => f.portal == 'A') ? 'A' : 'CC' : 'CC';
      //       ctx.fs.api.orgtype = authorizeRes.userInfo.orgtype;
      //       ctx.fs.api.userInfo = authorizeRes.userInfo;
      //       ctx.fs.api.organizationId = authorizeRes.userInfo.orgId;
      //    }
      // }
    } else {
      error = { status: 401, name: "Unauthorized" };
    }
    if (error) {
      ctx.fs.logger.log("[AUTH] failed", path, method);
      ctx.status = error.status;
      ctx.body = error.name;
    } else {
      ctx.fs.logger.log("[AUTH] passed", path, method);
      await next();
    }
  };
}

module.exports = factory;
